package service

import (
	"context"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"github.com/nsqio/go-nsq"
	"gsms/internal/conf"
	"gsms/internal/dao"
	"gsms/internal/model"
	"gsms/internal/utils"
)

const (
	VendorNameYunPian      = "yunpian"
	VendorNameAliyun       = "aliyun"
	VendorNameDummy        = "dummy"
	VendorNameTencentCloud = "tencent_cloud"
)

type SmsService struct {
	dao    *dao.Dao
	vendor SmsVendor
}

func New(c *conf.Config) (*SmsService, error) {
	d := dao.New(c)
	vendor, err := VendorFactory{}.CreateVendor(c)
	if err != nil {
		return nil, err
	}
	s := &SmsService{dao: d, vendor: vendor}
	// 监听NSQ
	s.addConsumer(model.TopicSendSMS, &smsSendHandler{s: s})
	return s, nil
}

func (s *SmsService) addConsumer(topic string, handler nsq.Handler) {
	if handler == nil {
		return
	}
	s.dao.StartWorker(topic, handler)
}

func (s *SmsService) Send(ctx context.Context, sms model.SmsVerify, sh model.SmsHistory) error {
	if sms.Code == "" {
		sms.Code = utils.GenerateVerifyCode()
	}
	if err := s.dao.DB.Transaction(func(tx *gorm.DB) error {
		if e := tx.Model(&model.SmsHistory{}).Where(map[string]interface{}{"request_id": sh.RequestID}).First(&sh).Error; e != nil && e != gorm.ErrRecordNotFound {
			return e
		} else if e == nil {
			return model.ErrDuplicate
		}

		if err := tx.Create(&sh).Error; err != nil {
			return err
		}
		// 插入or更新验证码表
		if e := tx.Model(&model.SmsVerify{}).Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "receiver"}, {Name: "sender"}, {Name: "verify_type"}},
			DoUpdates: clause.AssignmentColumns([]string{"code", "updated_at", "status", "created_at"}),
		}).Create(&sms).Error; e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}

	// 使用短信服务商的客户端发送短信
	text := fmt.Sprintf("您的验证码是%v，5分钟内有效", sms.Code)
	if err := s.vendor.sendCode(ctx, sms.To, sms.Code); err != nil {
		sh.Status = model.SmsSentFailed
		// 记录失败状态
		if e := s.SaveSmsHistory(sh); e != nil {
			return e
		}
		return err
	}
	// 后置事务记录短信发送结果
	sh.Text = text
	sh.Status = model.SmsSent
	sh.VendorName = s.vendor.getName()
	err := s.SaveSmsHistory(sh)
	if err != nil {
		return err
	}
	return nil
}

func (s *SmsService) SaveSmsHistory(sh model.SmsHistory) error {
	if err := s.dao.DB.Transaction(func(tx *gorm.DB) error {
		if e := tx.Save(&sh).Error; e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

func (s *SmsService) Verify(sms model.SmsVerify) (bool, error) {

	var matched bool
	if err := s.dao.DB.Transaction(func(tx *gorm.DB) error {
		if e := tx.Set("gorm:query_option", "FOR UPDATE").First(&sms, model.SmsVerify{To: sms.To, From: sms.From, VerifyType: sms.VerifyType, Status: sms.Status, Code: sms.Code}).Error; e == nil {
			matched = sms.IsValid()
			if matched {
				// 置验证码状态为已使用
				if err := tx.Model(&sms).Update("status", model.SmsStatusUsed).Error; err != nil {
					return err
				}
			}

		} else {
			matched = false
		}
		return nil
	}); err != nil {
		return matched, err
	}

	return matched, nil
}

func (s *SmsService) Close(ctx context.Context) {
	s.dao.Close(ctx)
}
